using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

using Bots.DungeonBuddy.Attributes;
using Bots.DungeonBuddy.Helpers;

using Styx;
using Styx.Helpers;
using Styx.Logic;
using Styx.Logic.Combat;
using Styx.Combat.CombatRoutine;
using Styx.Logic.Pathing;
using Styx.WoWInternals;
using Styx.WoWInternals.WoWObjects;
using TreeSharp;
using Action = TreeSharp.Action;


namespace Bots.DungeonBuddy.Dungeon_Scripts.Cataclysm
{
    public class TheDragonSoul : Dungeon
    {
        #region Overrides of Dungeon
        public override uint DungeonId
        {
            get { return 447; }
        }
        public override WoWPoint Entrance { get { return new WoWPoint(-8263.087f, -4513.621f, -219.7801f); } }

        /// <summary>
        ///   IncludeTargetsFilter is used to add units to the targeting list. If you want to include a mob thats usually removed by the default filters, you shall use that.
        /// </summary>
        /// <param name="incomingunits"> Units passed into the method </param>
        /// <param name="outgoingunits"> Units passed to the targeting class </param>
        public override void IncludeTargetsFilter(List<WoWObject> incomingunits, HashSet<WoWObject> outgoingunits)
        {
            foreach (var obj in incomingunits)
            {
                var unit = obj as WoWUnit;
                if (unit != null)
                {

                }
            }
        }

        /// <summary>RemoveTargetsFilter is used to remove units thats not wanted in target list. Like immune mobs etc.</summary>
        /// <param name="units">The incomingunits.</param>
        public override void RemoveTargetsFilter(List<WoWObject> units)
        {
            foreach (var obj in units)
            {
                var unit = obj as WoWUnit;
                if (unit != null)
                {

                }
            }
        }
        public override void WeighTargetsFilter(List<Targeting.TargetPriority> units)
        {
            foreach (var priority in units)
            {
                var unit = priority.Object as WoWUnit;
                if (unit != null)
                {
                    //Madness, Ship, Hagara
                    //Blistering Tentacles, Twilight Sapper, Frozen Binding Crystal,Bound Lightning Elemental
                    /*if (unit.Entry.Equals(new uint[] { 56188, 56923, 56136, 56700 }))
                    {
                        priority.Score += 400;
                    }*/
                    //Yor'Sahj
                    //add the bubbles in the needed priority

                    //Zonozz Hardmode

                    //Spine of Deathwing
                    //Hideous Amalgamation, Corrupted Blood, Corruption with Fiery Grip, Burning Tendron
                }
            }
        }
        #endregion
        //Copied from Singular
        private static int GetRadiusClusterCount(WoWGameObject target, IEnumerable<WoWGameObject> otherObjects, float radius)
        {
            var targetLoc = target.Location;
            return otherObjects.Count(u => u.Location.DistanceSqr(targetLoc) <= radius * radius);
        }
        //Copied from Singular, modified a little bit
        public static WoWGameObject GetBestObjectForCluster(IEnumerable<WoWGameObject> objects, float clusterRange)
        {
            if (!objects.Any())
                return null;
            return (from u in objects
                    select new { Count = GetRadiusClusterCount(u, objects, clusterRange), Unit = u }).OrderBy(a => a.Count).
                        FirstOrDefault().Unit;
        }
        #region Encounter Handlers
        /// <summary>
        ///   Using 0 as BossEntry will make that composite the main logic for the dungeon and it will be called in every tick You can only have one main logic for a dungeon The context of the main composite is all units around as List <WoWUnit />
        /// </summary>
        /// <returns> </returns>
        [EncounterHandler(0)]
        public Composite RootLogic()
        {
            return
                new PrioritySelector(
                // Handle trash with SpreadOut
                    ScriptHelpers.CreateSpreadOutLogic(ctx => GetBoulderSmashTarget() >= 1, 20)
                    );
        }
        #region Morchock
        private static ulong lastPillarGuid = 0;
        private static ulong lastCrytalGuid = 0;
        private static uint resonatingCrystals = 55346;
        private static uint InnerWall = 209596;
        private static WoWGameObject MorchockPillars
        {
            get
            {
                var oldPillar = ObjectManager.GetObjectsOfType<WoWGameObject>().Where(w => w.Guid == lastPillarGuid).FirstOrDefault();
                if (oldPillar != null) { return oldPillar; } else { lastPillarGuid = 0; }
                if (lastPillarGuid == 0)
                {
                    var Pillar = GetBestObjectForCluster(ObjectManager.GetObjectsOfType<WoWGameObject>().Where(w => w.Entry == InnerWall).OrderBy(w => w.Distance).AsEnumerable<WoWGameObject>(), 8);
                    if (Pillar != null)
                    {
                        lastPillarGuid = Pillar.Guid;
                    }
                    return Pillar;
                }
                return null;
            }
        }
        private static WoWUnit MorchockCrystals
        {
            get
            {
                return ObjectManager.GetObjectsOfType<WoWUnit>().Where(q => q.Entry == resonatingCrystals).FirstOrDefault();
            }
        }
        [EncounterHandler(55265, "Morchock", Mode = CallBehaviorMode.CurrentBoss, BossRange = 200)]
        public Composite MorchockEncounter()
        {
            var tankBossLoc = new WoWPoint(-2017.184, -2407.691, 70.53547);
            var bossFarPathEndLoc = new WoWPoint(-2005.624, -2409.554, 70.20834);
            var trashTankLoc = new WoWPoint(-2054.229, -2406.151, 73.31179);
            var partyWaitLoc = new WoWPoint(-2054.229, -2406.151, 73.31179);

            WoWUnit boss = null, trash = null;
            return new PrioritySelector(
                ctx => boss = ctx as WoWUnit,
                // Clear the area and then pull boss.
                new Decorator(ctx => boss != null,
                    new PrioritySelector(
                new Decorator(
                    ctx => !boss.Combat,
                    new PrioritySelector(
                        ctx => trash = ScriptHelpers.GetUnfriendlyNpsAtLocation(() => tankBossLoc, 50, u => u != boss).FirstOrDefault(),
                // pull the trash when boss is far away
                        ScriptHelpers.CreatePullNpcToLocation(ctx => trash != null && !StyxWoW.Me.Combat, ctx => boss.Location.DistanceSqr(bossFarPathEndLoc) <= 25 * 25 || trash.Location.DistanceSqr(trashTankLoc) <= 50 * 50, () => trash, () => trashTankLoc, () => StyxWoW.Me.Role == WoWPartyMember.GroupRole.Tank ? trashTankLoc : partyWaitLoc, 5),
                // pull boss when he's near and trash is cleared.
                        ScriptHelpers.CreatePullNpcToLocation(ctx => trash == null && StyxWoW.Me.Location.DistanceSqr(tankBossLoc) <= 50 * 50, ctx => boss.Location.DistanceSqr(tankBossLoc) <= 30 * 30, () => boss, () => tankBossLoc, () => StyxWoW.Me.Role == WoWPartyMember.GroupRole.Tank ? tankBossLoc : StyxWoW.Me.Location, 5)
                        )
                ),
                // Handle boss encounter
                new Decorator(
                    ctx => boss.Combat,
                    new PrioritySelector(
                        new Decorator(ret => MorchockPillars != null,
                            new PrioritySelector(
                            //Black Blood - Currently tested by buzzerbeater
                            ScriptHelpers.CreateRunAwayFromLocation(ret => StyxWoW.Me.HasAura(103785) && !StyxWoW.Me.HasAura(110047), 10, () => WoWMovement.CalculatePointFrom(StyxWoW.Me.Location, -15)),
                            //Rocks
                            ScriptHelpers.CreateLosLocation(ret => MorchockPillars != null && !StyxWoW.Me.HasAura(103785) && !StyxWoW.Me.HasAura(110047), () => boss.Location, () => MorchockPillars.Location, () => 5f)
                        )),

                //103687 - Crush Armor                            
                new Decorator(ret => boss!=null && StyxWoW.Me.Role == WoWPartyMember.GroupRole.Tank && boss.ThreatInfo.RawPercent < 100 && GetStacksOfDebuff(boss.CurrentTarget, "Crush Armor") > 3, CreateTankTaunt(boss)),
                //Tanking
                        ScriptHelpers.CreateTankUnitAtLocation(() => tankBossLoc, 20),
                        ScriptHelpers.CreateTankFaceAwayGroupUnit(17),
                //Crystals
                new Decorator(ret => MorchockCrystals != null, ScriptHelpers.CreateMoveToContinue(ret => MorchockCrystals != null && StyxWoW.Me.Role != WoWPartyMember.GroupRole.Tank && MorchockCrystals.Distance > 10 && (StyxWoW.Me.HasAura(103536) || StyxWoW.Me.HasAura(103534) || StyxWoW.Me.HasAura(103541)), () => MorchockCrystals.Location, true))
                        )
                    )
                )
            )
        );
        }
        private int GetBoulderSmashTarget()
        {
            var BoulderSmashCount = 0;
            var EarthenDestroyers = ObjectManager.GetObjectsOfType<WoWUnit>().Where(q => q.Entry == 57158 && q.IsCasting && q.DistanceSqr <= 50).ToList();
            if (EarthenDestroyers != null)
            {
                foreach (var EarthenDestroyer in EarthenDestroyers)
                {
                    if (EarthenDestroyer.CastingSpellId == 107597)
                    {
                        var distanceBetweenMeAndBoulderSmashTarget = EarthenDestroyer.CurrentTarget.Distance;
                        if (distanceBetweenMeAndBoulderSmashTarget <= 20) BoulderSmashCount++;
                    }
                }
                return BoulderSmashCount;
            }
            return 0;
        }
        #endregion

        private static uint GetStacksOfDebuff(WoWUnit unit, string Debuff)
        {
            if (unit != null && unit.HasAura(Debuff))
            {
                return unit.Auras[Debuff].StackCount;
            }
            return 0;
        }

        private static Composite CreateTankTaunt(WoWUnit tar)
        {
            Logger.WriteDebug("*** CreateTankTaunt ***");
            string tauntSpell1 = null;
            string tauntSpell2 = null;
            switch (StyxWoW.Me.Class)
            {
                case WoWClass.DeathKnight:
                    tauntSpell1 = "Dark Command";
                    tauntSpell2 = "Death Grip";
                    break;
                case WoWClass.Druid:
                    tauntSpell1 = "Growl";
                    tauntSpell2 = "Challenging Roar";
                    break;
                case WoWClass.Paladin:
                    tauntSpell1 = "Hand of Reckoning";
                    tauntSpell2 = "Righteous Defense";
                    break;
                case WoWClass.Warrior:
                    tauntSpell1 = "Taunt";
                    tauntSpell2 = "Intervene";
                    break;
                default:
                    tauntSpell1 = "";
                    tauntSpell2 = "";
                    break;
            }
            Logger.WriteDebug("*** Class:{0}", StyxWoW.Me.Class);
            Logger.WriteDebug("*** Role:{0}", StyxWoW.Me.Role);
            if (tauntSpell1.Length > 0 && tauntSpell2.Length > 0) Logger.WriteDebug("*** Abilities:{0} ({1}), {2}({3})", tauntSpell1, SpellManager.CanCast(tauntSpell1), tauntSpell2, SpellManager.CanCast(tauntSpell2));
            if (tar != null) Logger.WriteDebug("*** Target: ID {0}, Name {1}, Your Aggro: ", tar.Entry, tar.Name, tar.ThreatInfo.RawPercent);
            return new Decorator(ret => tar != null,
                    new Decorator(ret => StyxWoW.Me.Role == WoWPartyMember.GroupRole.Tank && tar.ThreatInfo.RawPercent < 100,
                        new PrioritySelector(
                            new Decorator(
                                ret => SpellManager.CanCast(tauntSpell1),
                                new Action(ret =>
                                {
                                    SpellManager.Cast(tauntSpell1, tar);
                                    Logger.WriteDebug("*** Taunted {0} with {1}", tar.Name, tauntSpell1);
                                })),
                            new Decorator(
                                ret => SpellManager.CanCast(tauntSpell2),
                                    new Action(ret =>
                                    {
                                        SpellManager.Cast(tauntSpell2, tar);
                                        Logger.WriteDebug("*** Taunted {0} with {1}", tar.Name, tauntSpell2);
                                    }
                                    )
                                )
                            )
                        )
                    );
        }
        #endregion
    }
}